Asinxron iteratorlar yordamida JavaScript'da yuqori o'tkazuvchanlikka ega parallel protsessor yaratishni o'rganing. Ma'lumotlarga boy ilovalarni keskin tezlashtirish uchun bir vaqtning o'zida oqimlarni boshqarishni o'zlashtiring.
Yuqori unumdorlikdagi JavaScript'ni ochish: Bir vaqtning o'zida oqimlarni boshqarish uchun iterator yordamchisi parallel protsessorlariga chuqur kirish
Zamonaviy dasturiy ta'minot ishlab chiqish olamida unumdorlik shunchaki xususiyat emas, balki asosiy talabdir. Backend xizmatida katta hajmdagi ma'lumotlar to'plamini qayta ishlashdan tortib, veb-ilovada murakkab API o'zaro ta'sirlarini boshqarishgacha, asinxron operatsiyalarni samarali boshqarish qobiliyati birinchi darajali ahamiyatga ega. JavaScript o'zining bir oqimli, hodisalarga asoslangan modeli bilan uzoq vaqtdan beri I/O bilan bog'liq vazifalarni a'lo darajada bajargan. Biroq, ma'lumotlar hajmi ortib borishi bilan an'anaviy ketma-ket qayta ishlash usullari jiddiy to'siqlarga aylanadi.
10 000 ta mahsulot haqida ma'lumot olish, gigabayt hajmidagi log faylini qayta ishlash yoki yuzlab foydalanuvchi yuklagan rasmlar uchun eskizlar yaratish zarurligini tasavvur qiling. Bu vazifalarni birma-bir bajarish ishonchli, ammo juda sekin. Unumdorlikni keskin oshirishning kaliti bir vaqtdalikda — bir vaqtning o'zida bir nechta elementlarni qayta ishlashda yotadi. Aynan shu yerda asinxron iteratorlarning kuchi, maxsus parallel qayta ishlash strategiyasi bilan birgalikda, ma'lumotlar oqimlarini boshqarish usulimizni o'zgartiradi.
Ushbu keng qamrovli qo'llanma oddiy `async/await` sikllaridan tashqariga chiqishni istagan o'rta va yuqori darajadagi JavaScript dasturchilari uchun mo'ljallangan. Biz JavaScript iteratorlarining asoslarini o'rganamiz, ketma-ket to'siqlar muammosiga chuqur kirib boramiz va eng muhimi, boshidan kuchli, qayta ishlatiladigan Iterator Yordamchisi Parallel Protsessorini yaratamiz. Ushbu vosita har qanday ma'lumotlar oqimi bo'yicha bir vaqtning o'zida bajariladigan vazifalarni nozik nazorat bilan boshqarish imkonini beradi, bu esa ilovalaringizni tezroq, samaraliroq va kengaytiriladigan qiladi.
Asoslarni tushunish: Iteratorlar va asinxron JavaScript
Parallel protsessorimizni yaratishdan oldin, bunga imkon beruvchi asosiy JavaScript tushunchalarini: iterator protokollari va ularning asinxron analoglarini puxta tushunishimiz kerak.
Iteratorlar va Iterabllarning kuchi
Aslida, iterator protokoli qiymatlar ketma-ketligini yaratishning standart usulini taqdim etadi. Agar obyekt `Symbol.iterator` kalitiga ega metodni amalga oshirsa, u iterable (takrorlanuvchi) hisoblanadi. Bu metod `next()` metodiga ega bo'lgan iterator obyektini qaytaradi. `next()` ga qilingan har bir murojaat ikkita xususiyatga ega bo'lgan obyektni qaytaradi: `value` (ketma-ketlikdagi keyingi qiymat) va `done` (ketma-ketlik tugaganligini ko'rsatuvchi mantiqiy qiymat).
Ushbu protokol `for...of` siklining ortidagi sehr bo'lib, ko'plab o'rnatilgan turlarda tabiiy ravishda amalga oshirilgan:
- Massivlar: `['a', 'b', 'c']`
- Satrlar: `"hello"`
- Xaritalar (Maps): `new Map([['key1', 'value1'], ['key2', 'value2']])`
- To'plamlar (Sets): `new Set([1, 2, 3])`
Iterabllarning go'zalligi shundaki, ular ma'lumotlar oqimlarini "dangasa" usulda ifodalaydi. Siz qiymatlarni birma-bir olasiz, bu katta yoki hatto cheksiz ketma-ketliklar uchun xotira jihatidan juda samarali, chunki butun ma'lumotlar to'plamini bir vaqtning o'zida xotirada saqlashingiz shart emas.
Asinxron Iteratorlarning yuksalishi
Standart iterator protokoli sinxrondir. Agar ketma-ketligimizdagi qiymatlar darhol mavjud bo'lmasa-chi? Agar ular tarmoq so'rovi, ma'lumotlar bazasi kursori yoki fayl oqimidan kelsa-chi? Aynan shu yerda asinxron iteratorlar yordamga keladi.
Asinxron iterator protokoli o'zining sinxron hamkasbiga juda yaqin. Agar obyekt `Symbol.asyncIterator` kalitiga ega metodga ega bo'lsa, u asinxron iterable hisoblanadi. Bu metod asinxron iteratorni qaytaradi, uning `next()` metodi esa tanish `{ value, done }` obyektiga aylantiriladigan `Promise` ni qaytaradi.
Bu bizga vaqt o'tishi bilan keladigan ma'lumotlar oqimlari bilan ishlash imkonini beradi va buning uchun nafis `for await...of` siklidan foydalaniladi:
Misol: Raqamlarni kechikish bilan qaytaradigan asinxron generator.
async function* createDelayedNumberStream() {
for (let i = 1; i <= 5; i++) {
// Tarmoq kechikishi yoki boshqa asinxron operatsiyani simulyatsiya qilish
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeStream() {
const numberStream = createDelayedNumberStream();
console.log('Starting consumption...');
// Sikl har bir 'await' da keyingi qiymat tayyor bo'lguncha to'xtab turadi
for await (const number of numberStream) {
console.log(`Received: ${number}`);
}
console.log('Consumption finished.');
}
// Natijada raqamlar har 500ms da paydo bo'ladi
Ushbu naqsh Node.js va brauzerlarda zamonaviy ma'lumotlarni qayta ishlash uchun asos bo'lib, katta ma'lumotlar manbalarini muammosiz boshqarish imkonini beradi.
Iterator Yordamchilari Taklifini tanishtirish
`for...of` sikllari kuchli bo'lsa-da, ular imperativ va ko'p so'zli bo'lishi mumkin. Massivlar uchun bizda `.map()`, `.filter()` va `.reduce()` kabi boy deklarativ metodlar to'plami mavjud. Iterator Yordamchilari TC39 taklifi xuddi shu ifodali kuchni to'g'ridan-to'g'ri iteratorlarga olib kelishni maqsad qilgan.
Ushbu taklif `Iterator.prototype` va `AsyncIterator.prototype` ga metodlar qo'shib, har qanday iterable manbada operatsiyalarni avval massivga o'tkazmasdan zanjir qilib bog'lash imkonini beradi. Bu xotira samaradorligi va kodning tushunarliligi uchun inqilobiy o'zgarishdir.
Ma'lumotlar oqimini filtrlash va xaritalash uchun ushbu "avval va keyin" ssenariysini ko'rib chiqing:
Avval (standart sikl bilan):
async function processData(source) {
const results = [];
for await (const item of source) {
if (item.value > 10) { // filtrlash
const processedItem = await transform(item); // xaritalash
results.push(processedItem);
}
}
return results;
}
Keyin (taklif etilgan asinxron iterator yordamchilari bilan):
async function processDataWithHelpers(source) {
const results = await source
.filter(item => item.value > 10)
.map(async item => await transform(item))
.toArray(); // .toArray() - bu yana bir taklif etilgan yordamchi
return results;
}
Ushbu taklif hali barcha muhitlarda tilning standart qismi bo'lmasa-da, uning prinsiplari bizning parallel protsessorimiz uchun kontseptual asosni tashkil etadi. Biz bir vaqtning o'zida bitta elementni qayta ishlaydigan emas, balki bir nechta `transform` operatsiyalarini parallel ravishda bajaradigan `map` ga o'xshash operatsiyani yaratmoqchimiz.
To'siq: Asinxron Dunyoda Ketma-ket Qayta Ishlash
`for await...of` sikli ajoyib vosita, lekin uning muhim bir xususiyati bor: u ketma-ket ishlaydi. Sikl tanasi keyingi element uchun joriy elementning `await` operatsiyalari to'liq tugamaguncha boshlanmaydi. Bu mustaqil vazifalar bilan ishlashda unumdorlik chegarasini yaratadi.
Keling, keng tarqalgan, real hayotiy ssenariy bilan misol keltiramiz: identifikatorlar ro'yxati uchun API'dan ma'lumotlarni olish.
Tasavvur qiling, bizda 100 ta foydalanuvchi ID'sini qaytaradigan asinxron iterator bor. Har bir ID uchun foydalanuvchi profilini olish uchun API so'rovini amalga oshirishimiz kerak. Har bir API so'rovi o'rtacha 200 millisekund davom etadi deb faraz qilaylik.
async function fetchUserProfile(userId) {
// API so'rovini simulyatsiya qilish
await new Promise(resolve => setTimeout(resolve, 200));
return { id: userId, name: `User ${userId}`, fetchedAt: new Date() };
}
async function fetchAllUsersSequentially(userIds) {
console.time('SequentialFetch');
const profiles = [];
for await (const id of userIds) {
const profile = await fetchUserProfile(id);
profiles.push(profile);
console.log(`Fetched user ${id}`);
}
console.timeEnd('SequentialFetch');
return profiles;
}
// 'userIds' - 100 ta ID'dan iborat asinxron iterable deb faraz qilamiz
// await fetchAllUsersSequentially(userIds);
Umumiy bajarilish vaqti qancha bo'ladi? Har bir `await fetchUserProfile(id)` keyingisi boshlanishidan oldin tugashi kerakligi sababli, umumiy vaqt taxminan quyidagicha bo'ladi:
100 foydalanuvchi * 200 ms/foydalanuvchi = 20 000 ms (20 soniya)
Bu klassik I/O bilan bog'liq to'siqdir. Bizning JavaScript jarayonimiz tarmoqni kutayotganda, uning hodisalar sikli asosan bo'sh turadi. Biz tizimning yoki tashqi API'ning to'liq quvvatidan foydalanmayapmiz. Qayta ishlash vaqt jadvali quyidagicha ko'rinadi:
Vazifa 1: [---KUTISH---] Bajarildi
Vazifa 2: [---KUTISH---] Bajarildi
Vazifa 3: [---KUTISH---] Bajarildi
...va hokazo.
Bizning maqsadimiz bu vaqt jadvalini 10 darajali bir vaqtdalikdan foydalanib, quyidagicha o'zgartirish:
Vazifa 1-10: [---KUTISH---][---KUTISH---]... Bajarildi
Vazifa 11-20: [---KUTISH---][---KUTISH---]... Bajarildi
...
10 ta bir vaqtda bajariladigan operatsiyalar bilan biz nazariy jihatdan umumiy vaqtni 20 soniyadan atigi 2 soniyaga qisqartirishimiz mumkin. Bu biz o'z parallel protsessorimizni yaratish orqali erishmoqchi bo'lgan unumdorlik sakrashidir.
JavaScript Iterator Yordamchisi Parallel Protsessorini Yaratish
Endi biz maqolaning asosiy qismiga yetib keldik. Biz `parallelMap` deb nomlaydigan, qayta ishlatiladigan asinxron generator funksiyasini yaratamiz. U asinxron iterable manba, mapper funksiyasi va bir vaqtdalik darajasini qabul qiladi. U qayta ishlangan natijalarni ular tayyor bo'lishi bilan qaytaradigan yangi asinxron iterableni hosil qiladi.
Asosiy Dizayn Prinsiplari
- Bir vaqtdalikni cheklash: Protsessor hech qachon bir vaqtning o'zida belgilangan sondan ortiq `mapper` funksiyasi promislari bilan ishlamasligi kerak. Bu resurslarni boshqarish va tashqi API'larning so'rovlar chegaralariga rioya qilish uchun juda muhim.
- Dangasa iste'mol: U manba iteratoridan faqat o'zining qayta ishlash hovuzida bo'sh joy bo'lgandagina ma'lumot olishi kerak. Bu butun manbani xotirada buferlashdan saqlaydi va oqimlarning afzalliklarini saqlab qoladi.
- Qayta bosimni boshqarish: Agar protsessorning natijasini iste'mol qiluvchi sekin ishlasa, u tabiiy ravishda to'xtashi kerak. Asinxron generatorlar bunga `yield` kalit so'zi orqali avtomatik ravishda erishadi. Bajarilish `yield` da to'xtatilganda, manbadan yangi elementlar olinmaydi.
- Maksimal o'tkazuvchanlik uchun tartibsiz natija: Eng yuqori tezlikka erishish uchun bizning protsessorimiz natijalarni ular tayyor bo'lishi bilan qaytaradi, bu esa kirish ma'lumotlarining asl tartibiga mos kelmasligi mumkin. Tartibni saqlashni keyinroq, ilg'or mavzu sifatida muhokama qilamiz.
`parallelMap` ning amalga oshirilishi
Keling, funksiyamizni qadamma-qadam yaratamiz. Maxsus asinxron iterator yaratish uchun eng yaxshi vosita bu `async function*` (asinxron generator).
/**
* Manba iterabledan elementlarni parallel ravishda qayta ishlaydigan yangi asinxron iterable yaratadi.
* @param {AsyncIterable|Iterable} source Qayta ishlash uchun manba iterable.
* @param {Function} mapperFn Elementni olib, qayta ishlangan natija promisini qaytaradigan asinxron funksiya.
* @param {object} options
* @param {number} options.concurrency Parallel ravishda bajariladigan vazifalarning maksimal soni.
* @returns {AsyncGenerator} Qayta ishlangan natijalarni qaytaradigan asinxron generator.
*/
async function* parallelMap(source, mapperFn, { concurrency = 5 }) {
// 1. Manbadan asinxron iteratorni oling.
// Bu ham sinxron, ham asinxron iterablelar uchun ishlaydi.
const asyncIterator = source[Symbol.asyncIterator] ?
source[Symbol.asyncIterator]() :
source[Symbol.iterator]();
// 2. Hozirda qayta ishlanayotgan vazifalar promislari hisobini yuritish uchun to'plam.
// Set'dan foydalanish promislarni qo'shish va o'chirishni samarali qiladi.
const processing = new Set();
// 3. Manba iteratori tugaganligini kuzatish uchun bayroqcha.
let sourceIsDone = false;
// 4. Asosiy sikl: qayta ishlanayotgan vazifalar mavjud bo'lganda
// yoki manbada yana elementlar bo'lganda davom etadi.
while (!sourceIsDone || processing.size > 0) {
// 5. Qayta ishlash hovuzini bir vaqtdalik chegarasigacha to'ldiring.
while (processing.size < concurrency && !sourceIsDone) {
const nextItemPromise = asyncIterator.next();
const processingPromise = nextItemPromise.then(item => {
if (item.done) {
sourceIsDone = true;
return; // Bu shoxobcha tugaganligini, qayta ishlash uchun natija yo'qligini bildiradi.
}
// Mapper funksiyasini bajaring va uning natijasi promis ekanligiga ishonch hosil qiling.
// Bu yakuniy qayta ishlangan qiymatni qaytaradi.
return Promise.resolve(mapperFn(item.value));
});
// Bu hovuzni boshqarish uchun juda muhim qadam.
// Biz o'ram-promis yaratamiz, u hal bo'lganda bizga ham
// yakuniy natijani, ham o'ziga havolani beradi, shunda biz uni hovuzdan olib tashlay olamiz.
const trackedPromise = processingPromise.then(result => ({
result,
origin: trackedPromise
}));
processing.add(trackedPromise);
}
// 6. Agar hovuz bo'sh bo'lsa, demak, biz tugatdik. Siklni to'xtating.
if (processing.size === 0) break;
// 7. Qayta ishlanayotgan vazifalardan BIRORTASINING tugashini kuting.
// Bunga erishishning kaliti Promise.race() dir.
const { result, origin } = await Promise.race(processing);
// 8. Tugallangan promisni qayta ishlash hovuzidan olib tashlang.
processing.delete(origin);
// 9. Natijani qaytaring, agar u 'done' signalidan kelgan 'undefined' bo'lmasa.
// Bu generatorni iste'molchi keyingi elementni so'ramaguncha to'xtatib turadi.
if (result !== undefined) {
yield result;
}
}
}
Mantiqni tahlil qilish
- Initsializatsiya: Biz manbadan asinxron iteratorni olamiz va bir vaqtdalik hovuzimiz sifatida xizmat qilish uchun `processing` nomli `Set` ni initsializatsiya qilamiz.
- Hovuzni to'ldirish: Ichki `while` sikli - bu dvigatel. U `processing` to'plamida bo'sh joy borligini va `source` da hali elementlar mavjudligini tekshiradi. Agar shunday bo'lsa, u keyingi elementni oladi.
- Vazifani bajarish: Har bir element uchun biz `mapperFn` ni chaqiramiz. Butun operatsiya — keyingi elementni olish va uni xaritalash — promis (`processingPromise`) ga o'ralgan.
- Promislarni kuzatish: Eng qiyin qismi `Promise.race()` dan keyin to'plamdan qaysi promisni olib tashlashni bilishdir. `Promise.race()` promis obyektining o'zini emas, balki hal qilingan qiymatni qaytaradi. Buni hal qilish uchun biz yakuniy `result` ni ham, o'ziga (`origin`) havolani ham o'z ichiga olgan obyektga aylanadigan `trackedPromise` yaratamiz. Biz ushbu kuzatuv promisini `processing` to'plamimizga qo'shamiz.
- Eng tez vazifani kutish: `await Promise.race(processing)` hovuzdagi birinchi vazifa tugamaguncha bajarilishni to'xtatib turadi. Bu bizning bir vaqtdalik modelimizning yuragi.
- Qaytarish va to'ldirish: Vazifa tugagach, uning natijasini olamiz. Biz unga mos keladigan `trackedPromise` ni `processing` to'plamidan olib tashlaymiz, bu esa bo'sh joy ochadi. Keyin natijani `yield` qilamiz. Iste'molchining sikli keyingi elementni so'raganda, bizning asosiy `while` siklimiz davom etadi va ichki `while` sikli bo'sh joyni manbadan yangi vazifa bilan to'ldirishga harakat qiladi.
Bu o'z-o'zini tartibga soluvchi konveyerni yaratadi. Hovuz doimiy ravishda `Promise.race` tomonidan bo'shatiladi va manba iteratoridan qayta to'ldiriladi, bu esa bir vaqtning o'zida bajariladigan operatsiyalarning barqaror holatini saqlaydi.
`parallelMap` dan foydalanish
Keling, foydalanuvchilarni olish misolimizga qaytaylik va yangi utilitamizni qo'llaylik.
// 'createIdStream' - 100 ta foydalanuvchi ID'sini qaytaradigan asinxron generator deb faraz qiling.
const userIdStream = createIdStream();
async function fetchAllUsersInParallel() {
console.time('ParallelFetch');
const profilesStream = parallelMap(userIdStream, fetchUserProfile, { concurrency: 10 });
for await (const profile of profilesStream) {
console.log(`Processed profile for user ${profile.id}`);
}
console.timeEnd('ParallelFetch');
}
// await fetchAllUsersInParallel();
10 darajali bir vaqtdalik bilan umumiy bajarilish vaqti endi 20 soniya o'rniga taxminan 2 soniyani tashkil etadi. Biz oqimimizni shunchaki `parallelMap` bilan o'rash orqali 10 barobar unumdorlikni oshirishga erishdik. Eng yaxshi tomoni shundaki, iste'mol qiluvchi kod oddiy, o'qilishi oson `for await...of` sikli bo'lib qoladi.
Amaliy qo'llash holatlari va global misollar
Ushbu naqsh faqat foydalanuvchi ma'lumotlarini olish uchun emas. Bu global ilovalar ishlab chiqishda keng tarqalgan ko'plab muammolarga qo'llanilishi mumkin bo'lgan ko'p qirrali vositadir.
Yuqori o'tkazuvchanlikka ega API o'zaro ta'sirlari
Ssenariy: Moliyaviy xizmatlar ilovasi tranzaksiya ma'lumotlari oqimini boyitishi kerak. Har bir tranzaksiya uchun u ikkita tashqi API'ga murojaat qilishi kerak: biri firibgarlikni aniqlash uchun, ikkinchisi valyuta konvertatsiyasi uchun. Ushbu API'lar soniyasiga 100 ta so'rov chegarasiga ega.
Yechim: Tranzaksiyalar oqimini qayta ishlash uchun `concurrency` ni `20` yoki `30` qilib belgilangan `parallelMap` dan foydalaning. `mapperFn` `Promise.all` yordamida ikkita API so'rovini amalga oshiradi. Bir vaqtdalik chegarasi API so'rovlar chegarasidan oshib ketmasdan yuqori o'tkazuvchanlikka erishishingizni ta'minlaydi, bu uchinchi tomon xizmatlari bilan ishlaydigan har qanday ilova uchun muhim masaladir.
Katta hajmdagi ma'lumotlarni qayta ishlash va ETL (Chiqarish, O'zgartirish, Yuklash)
Ssenariy: Node.js muhitidagi ma'lumotlar tahlili platformasi bulutli omborda (masalan, Amazon S3 yoki Google Cloud Storage) saqlangan 5 GB hajmdagi CSV faylini qayta ishlashi kerak. Har bir qatorni tekshirish, tozalash va ma'lumotlar bazasiga kiritish kerak.
Yechim: Bulutli ombor oqimidan faylni qatorma-qator o'qiydigan asinxron iterator yarating (masalan, Node.js'dagi `stream.Readable` yordamida). Ushbu iteratorni `parallelMap` ga yo'naltiring. `mapperFn` tekshirish mantiqini va ma'lumotlar bazasiga `INSERT` operatsiyasini bajaradi. `concurrency` ni ma'lumotlar bazasining ulanishlar hovuzi hajmiga qarab sozlash mumkin. Ushbu yondashuv 5 GB faylni xotiraga yuklashdan saqlaydi va konveyerning sekin ishlaydigan ma'lumotlar bazasiga kiritish qismini parallellashtiradi.
Rasm va video transkodlash konveyeri
Ssenariy: Global ijtimoiy media platformasi foydalanuvchilarga video yuklash imkonini beradi. Har bir video bir nechta o'lchamlarga (masalan, 1080p, 720p, 480p) transkodlanishi kerak. Bu CPU'ni ko'p talab qiladigan vazifadir.
Yechim: Foydalanuvchi bir nechta video yuklaganda, video fayl yo'llaridan iborat iterator yarating. `mapperFn` `ffmpeg` kabi buyruqlar qatori vositasini ishga tushirish uchun yordamchi jarayonni yaratadigan asinxron funksiya bo'lishi mumkin. `concurrency` ni tizimni ortiqcha yuklamasdan apparat resurslaridan maksimal darajada foydalanish uchun mashinadagi mavjud CPU yadrolari soniga (masalan, Node.js'dagi `os.cpus().length`) teng qilib belgilash kerak.
Ilg'or tushunchalar va mulohazalar
`parallelMap` imiz kuchli bo'lsa-da, real hayotiy ilovalar ko'pincha ko'proq nozikliklarni talab qiladi.
Ishonchli xatolarni boshqarish
`mapperFn` chaqiruvlaridan biri rad etilsa nima bo'ladi? Bizning hozirgi amalga oshirishimizda `Promise.race` rad etadi, bu esa butun `parallelMap` generatorining xato chiqarishiga va to'xtashiga olib keladi. Bu "tezda ishdan chiqish" strategiyasidir.
Ko'pincha, siz alohida xatoliklarga bardosh bera oladigan yanada barqaror konveyerni xohlaysiz. Bunga `mapperFn` ni o'rash orqali erishishingiz mumkin.
const resilientMapper = async (item) => {
try {
return { status: 'fulfilled', value: await originalMapper(item) };
} catch (error) {
console.error(`Failed to process item ${item.id}:`, error);
return { status: 'rejected', reason: error, item: item };
}
};
const resultsStream = parallelMap(source, resilientMapper, { concurrency: 10 });
for await (const result of resultsStream) {
if (result.status === 'fulfilled') {
// muvaffaqiyatli qiymatni qayta ishlash
} else {
// xatolikni qayta ishlash yoki jurnalga yozish
}
}
Tartibni saqlash
Bizning `parallelMap` imiz tezlikni birinchi o'ringa qo'yib, natijalarni tartibsiz qaytaradi. Ba'zan, chiqish tartibi kirish tartibiga mos kelishi kerak. Bu `parallelOrderedMap` deb ataladigan boshqacha, murakkabroq amalga oshirishni talab qiladi.
Tartiblangan versiya uchun umumiy strategiya quyidagicha:
- Elementlarni avvalgidek parallel ravishda qayta ishlash.
- Natijalarni darhol qaytarish o'rniga, ularni asl indekslari bo'yicha kalitlangan bufer yoki xaritada saqlash.
- Qaytarilishi kutilayotgan keyingi indeks uchun hisoblagichni yuritish.
- Siklda, joriy kutilayotgan indeks uchun natija buferda mavjudligini tekshirish. Agar mavjud bo'lsa, uni qaytarish, hisoblagichni oshirish va takrorlash. Agar yo'q bo'lsa, ko'proq vazifalarning tugashini kutish.
Bu bufer uchun qo'shimcha yuklama va xotira sarfini keltirib chiqaradi, ammo tartibga bog'liq ish jarayonlari uchun zarurdir.
Qayta bosim tushuntirildi
Ushbu asinxron generatorga asoslangan yondashuvning eng nafis xususiyatlaridan birini takrorlashga arziydi: avtomatik qayta bosimni boshqarish. Agar bizning `parallelMap` imizni iste'mol qiladigan kod sekin bo'lsa — masalan, har bir natijani sekin diskka yoki tiqilib qolgan tarmoq soketiga yozayotgan bo'lsa — `for await...of` sikli keyingi elementni so'ramaydi. Bu bizning generatorimizni `yield result;` qatorida to'xtashiga olib keladi. To'xtatilgan vaqtda u siklga kirmaydi, `Promise.race` ni chaqirmaydi va eng muhimi, qayta ishlash hovuzini to'ldirmaydi. Bu talabning yo'qligi asl manba iteratorigacha tarqaladi va undan ma'lumot o'qilmaydi. Butun konveyer avtomatik ravishda o'zining eng sekin komponentining tezligiga moslashib sekinlashadi, bu esa ortiqcha buferlash tufayli xotiraning to'lib ketishini oldini oladi.
Xulosa va kelajak istiqbollari
Biz JavaScript iteratorlarining asosiy tushunchalaridan boshlab, murakkab, yuqori unumdorlikka ega parallel qayta ishlash utilitasini yaratishgacha bo'lgan yo'lni bosib o'tdik. Ketma-ket `for await...of` sikllaridan boshqariladigan bir vaqtdalik modeliga o'tish orqali, biz ma'lumotlarga boy, I/O bilan bog'liq va CPU bilan bog'liq vazifalar uchun unumdorlikni bir necha barobar oshirishga qanday erishish mumkinligini ko'rsatdik.
Asosiy xulosalar:
- Ketma-ketlik sekin: An'anaviy asinxron sikllar mustaqil vazifalar uchun to'siqdir.
- Bir vaqtdalik muhim: Elementlarni parallel ravishda qayta ishlash umumiy bajarilish vaqtini keskin qisqartiradi.
- Asinxron generatorlar ideal vosita: Ular qayta bosim kabi muhim xususiyatlarni o'z ichiga olgan maxsus iterablelarni yaratish uchun toza abstraksiyani ta'minlaydi.
- Nazorat muhim: Boshqariladigan bir vaqtdalik hovuzi resurslarning tugashini oldini oladi va tashqi tizim cheklovlariga rioya qiladi.
JavaScript ekotizimi rivojlanishda davom etar ekan, Iterator Yordamchilari taklifi, ehtimol, tilning standart qismiga aylanadi va oqimlarni manipulyatsiya qilish uchun mustahkam, tabiiy asosni ta'minlaydi. Biroq, parallellashtirish mantig'i — `Promise.race` kabi vosita yordamida promislari hovuzini boshqarish — dasturchilar muayyan unumdorlik muammolarini hal qilish uchun amalga oshirishi mumkin bo'lgan kuchli, yuqori darajadagi naqsh bo'lib qoladi.
Men sizni bugun biz yaratgan `parallelMap` funksiyasini olib, o'z loyihalaringizda tajriba qilib ko'rishga undayman. O'zingizning to'siqlaringizni aniqlang, ular API so'rovlari, ma'lumotlar bazasi operatsiyalari yoki fayllarni qayta ishlash bo'ladimi, va bu bir vaqtning o'zida oqimlarni boshqarish naqshi ilovalaringizni qanday qilib tezroq, samaraliroq va ma'lumotlarga asoslangan dunyo talablariga tayyor qilishini ko'ring.